/*
 *This file initializes the filesystem.
 *In addition, we provide necessary functions to parse and setup
 * the partition table for the system.
 *The MSDOS partition parsing function is modified from Linux source.
 *Please atribute your credits to Linux developpers, if you decide to do so.
 *This code is for educational purposes only, please do respect GPL 
 * if you are considering using this code.
 */
#include <cnix/fs.h>
#include <cnix/kernel.h>
#include <cnix/partition.h>
#include <cnix/kernel.h>
#include <cnix/string.h>

void fs_init(void)
{
	init_super();
	init_inode();
	init_filp();
}

struct partition_table sd_partition_table;

#define MSDOS_LABEL_MAGIC1  0x55
#define MSDOS_LABEL_MAGIC2  0xAA

static inline int
msdos_magic_present(unsigned char *p)
{
  return (p[0] == MSDOS_LABEL_MAGIC1 && p[1] == MSDOS_LABEL_MAGIC2);
}

/* media of boot sector */
static inline int fat_valid_media(u8_t media)
{     
  return 0xf8 <= media || media == 0xf0;
} 

static inline int is_extended_partition(struct partition *p)
{
  return (p->sys_ind == DOS_EXTENDED_PARTITION ||
    p->sys_ind == WIN98_EXTENDED_PARTITION ||
    p->sys_ind == LINUX_EXTENDED_PARTITION);
}

static inline void
put_partition(struct partition_table *p, sector_t from, sector_t size)
{
	p->nr_parts ++;
  if (p->nr_parts < DISK_MAX_PARTS) {
    p->parts[p->nr_parts].from = from;
    p->parts[p->nr_parts].size = size;
    printk(" %s%d", p->name, p->nr_parts);
  } 
}  

/*
 * Create devices for each logical partition in an extended partition.
 * The logical partitions form a linked list, with each entry being
 * a partition table with two entries.  The first entry
 * is the real data partition (with a start relative to the partition
 * table start).  The second is a pointer to the next logical partition
 * (with a start relative to the entire extended partition).
 * We do not create a Linux partition for the partition tables, but
 * only for the actual data partitions.
 */
static void
parse_extended(struct partition_table *pt, 
      sector_t first_sector, sector_t first_size)
{
  struct partition *p;
  sector_t this_sector, this_size;
	int i;
	struct buf_head *bh;

  this_sector = first_sector;
  this_size = first_size;

  while (1) {
		bh = sd_bread(DEVNUM(HD_MJ_DEV,0),this_sector);

    if (!msdos_magic_present(bh->b_data + 510))
      goto done;

    p = (struct partition *) (bh->b_data + 0x1be);
    /*
     * Usually, the first entry is the real data partition,
     * the 2nd entry is the next extended partition, or empty,
     * and the 3rd and 4th entries are unused.
     * However, DRDOS sometimes has the extended partition as
     * the first entry (when the data partition is empty),
     * and OS/2 seems to use all four entries.
     */

    /* 
     * First process the data partition(s)
     */
    for (i=0; i<4; i++, p++) {
      sector_t offs, size, next;
      if (!p->num_sec || is_extended_partition(p))
        continue;

      /* Check the 3rd and 4th entries -
         these sometimes contain random garbage */
      offs = p->start_sec;
      size = p->num_sec;
      next = this_sector + offs;
      if (i >= 1) {
					/*
					 *FIXME:
					 * I ignore partitions 2, 3 and 4 for now
				   * in order to get rid of unimportant complexities.
					 * Feel free to fixme is you really need this.
					 */
          continue;
      }

      put_partition(pt,next, size);
    }
    /*
     * Next, process the (first) extended partition, if present.
     * (So far, there seems to be no reason to make
     *  parse_extended()  recursive and allow a tree
     *  of extended partitions.)
     * It should be a link to the next logical partition.
     */
    p -= 4;
    for (i=0; i<4; i++, p++)
      if (p->num_sec && is_extended_partition(p))
        break;
    if (i == 4)
      goto done;   /* nothing left to do */

    this_sector = first_sector + p->start_sec;
    this_size = p->num_sec;
		brelse(bh);
  }
done:
 	brelse(bh); 
}

void parse_partition(struct partition_table *pt)
{
	struct buf_head *bh;
	struct partition *p;
	int slot;
	struct fat_boot_sector *fb;

	strcpy(pt->name,"sda");

	bh = sd_bread(DEVNUM(HD_MJ_DEV,0),0);

	if(!msdos_magic_present(bh->b_data + 510)){
		printk("Invalid partition table. %s %s\n",
			__func__,__FILE__);
		BUG();
	}

  /*
   * Now that the 55aa signature is present, this is probably
   * either the boot sector of a FAT filesystem or a DOS-type
   * partition table. Reject this in case the boot indicator
   * is not 0 or 0x80.
   */
  p = (struct partition *) (bh->b_data + 0x1be);
  for (slot = 1; slot <= 4; slot++, p++) {
    if (p->boot_ind != 0 && p->boot_ind != 0x80) {
      /*
       * Even without a valid boot inidicator value
       * its still possible this is valid FAT filesystem
       * without a partition table.
       */
      fb = (struct fat_boot_sector *) bh->b_data;
      if (slot == 1 && fb->reserved && fb->fats
        && fat_valid_media(fb->media)) {
				printk("FAT file system without a partition table? %s %s\n",
					__func__,__FILE__);
				BUG();
      } else {
				printk("Invalid partition table. %s %s\n",
					__func__,__FILE__);
				BUG();
      }
    }
  }

  p = (struct partition *) (bh->b_data + 0x1be);

  /*  
   * Find the primary and DOS-type extended partitions.
   */
	printk("Partition table of the system: \n");
  for (slot = 1 ; slot <= 4 ; slot++, p++) {
    sector_t start = p->start_sec;
    sector_t size = p->num_sec;
    if (!size){
			printk("empty partition! %s %s\n",__func__,__FILE__);
			BUG();
		}
    if (is_extended_partition(p)) {
      /*  
       * prevent someone doing mkfs or mkswap on an
       * extended partition, but leave room for LILO
       * FIXME: this uses one logical sector for > 512b
       * sector, although it may not be enough/proper.
       */
      sector_t n = 2;
      n = (size<2) ? size:2;
      put_partition(pt, start, n); 

			pt->nr_parts = 4;
      printk(" <");
      parse_extended(pt, start, size);
      printk(" >\n");
     	break;
    }   
    put_partition(pt, start, size);
  }

#if 1
	int i;
	printk("Partition table dump: \n");
	for(i=0;i<=pt->nr_parts;i++){
		if(pt->parts[i].from || pt->parts[i].size)
			printk("sda%d start: %u size: %u\n",i,
				(unsigned)pt->parts[i].from,
				(unsigned)pt->parts[i].size);
	}
#endif

	brelse(bh);
}

sector_t locate_block(dev_t dev, sector_t blk)
{
	int part_no = MINOR(dev);

	if(part_no < 0 || part_no >= DISK_MAX_PARTS ||
		 (!sd_partition_table.parts[part_no].from &&
		 !sd_partition_table.parts[part_no].size)){
		printk("Invalid partition number: %d\n",part_no);
		BUG();
	}
	
	return (sd_partition_table.parts[part_no].from + blk);
}
